package com.nulldev.util.io;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.zip.Deflater;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.Inflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import com.nulldev.util.java.JVMUtil;
import com.nulldev.util.other.ArrayUtils;
import com.nulldev.util.other.ByteUtils;
import com.nulldev.util.other.CharsetUtil;

public class ZipUtils
{
  private static final int  BUFFER_SIZE = 4096;

  private static void extractFile(ZipInputStream in, File outdir, String name) throws IOException
  {
    byte[] buffer = new byte[BUFFER_SIZE];
    BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(new File(outdir,name)));
    int count = -1;
    while ((count = in.read(buffer)) != -1)
      out.write(buffer, 0, count);
    out.close();
  }

  private static void mkdirs(File outdir,String path)
  {
    File d = new File(outdir, path);
    if( !d.exists() )
      d.mkdirs();
  }

  private static String dirpart(String name)
  {
    int s = name.lastIndexOf( File.separatorChar );
    return s == -1 ? null : name.substring( 0, s );
  }

  public static void extract(File zipfile, File outdir)
  {
    try
    {
      ZipInputStream zin = new ZipInputStream(new FileInputStream(zipfile));
      ZipEntry entry;
      String name, dir;
      while ((entry = zin.getNextEntry()) != null)
      {
        name = entry.getName();
        if( entry.isDirectory() )
        {
          mkdirs(outdir,name);
          continue;
        }
        dir = dirpart(name);
        if( dir != null )
          mkdirs(outdir,dir);

        extractFile(zin, outdir, name);
      }
      zin.close();
    }
    catch (IOException e)
    {
      e.printStackTrace();
    }
  }

  public static byte[] zip(final String str) {
      if ((str == null) || (str.length() == 0)) {
          throw new IllegalArgumentException("Cannot zip null or empty string");
      }

      final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
	  try {
		  final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);
	      gzipOutputStream.write(str.getBytes(CharsetUtil.getCharset("UTF-8")));
	      gzipOutputStream.close();
	  }catch (Exception e) {
		  throw new RuntimeException("Failed to zip content", e);
	  }
	  return byteArrayOutputStream.toByteArray();
  }

  public static byte[] zip16(final String str) {
      if ((str == null) || (str.length() == 0)) {
          throw new IllegalArgumentException("Cannot zip null or empty string");
      }

      final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
	  try {
		  final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);
	      gzipOutputStream.write(str.getBytes(CharsetUtil.getCharset("UTF-8")));
	      gzipOutputStream.close();
	  }catch (Exception e) {
		  throw new RuntimeException("Failed to zip content", e);
	  }
	  return byteArrayOutputStream.toByteArray();
  }
  
  public static byte[] zip16(final byte[] bytes) {
	  if (bytes == null) {
		  throw new RuntimeException("Cannot zip a null byte array!");
	  }
      final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
	  try {
		  final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);
	      gzipOutputStream.write(bytes);
	      gzipOutputStream.close();
	  }catch (Exception e) {
		  throw new RuntimeException("Failed to zip content", e);
	  }
	  return byteArrayOutputStream.toByteArray();
  }

  public static String unzip(final byte[] compressed) {
      if ((compressed == null) || (compressed.length == 0)) {
          throw new IllegalArgumentException("Cannot unzip null or empty bytes");
      }
      if (!isZipped(compressed)) {
          return new String(compressed);
      }

      final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(compressed);
		try {
			final GZIPInputStream gzipInputStream = new GZIPInputStream(byteArrayInputStream);
		    try {
		    	InputStreamReader inputStreamReader = new InputStreamReader(gzipInputStream, CharsetUtil.getCharset("UTF-8"));
		        try {
		        	BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
		            StringBuilder output = new StringBuilder();
		            String line;
		            while((line = bufferedReader.readLine()) != null){
		                output.append(line);
		            }
		            return output.toString();
		        }catch (Exception e) {
		        	throw new RuntimeException("Failed to unzip content", e);
		        }
		    }catch (Exception e) {
		    	throw new RuntimeException("Failed to unzip content", e);
		    }
		}catch (Exception e) {
			throw new RuntimeException("Failed to unzip content", e);
		}
  }

  public static String unzip16(final byte[] compressed) {
      if ((compressed == null) || (compressed.length == 0)) {
          throw new IllegalArgumentException("Cannot unzip null or empty bytes");
      }
      if (!isZipped(compressed)) {
          return new String(compressed);
      }

      final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(compressed);
		try {
			final GZIPInputStream gzipInputStream = new GZIPInputStream(byteArrayInputStream);
		    try {
		    	InputStreamReader inputStreamReader = new InputStreamReader(gzipInputStream, CharsetUtil.getCharset("UTF-8"));
		        try {
		        	BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
		            StringBuilder output = new StringBuilder();
		            String line;
		            while((line = bufferedReader.readLine()) != null){
		                output.append(line);
		            }
		            return output.toString();
		        }catch (Exception e) {
		        	throw new RuntimeException("Failed to unzip content" + JVMUtil.lineSep() + JVMUtil.fromError(e));
		        }
		    }catch (Exception e) {
		    	throw new RuntimeException("Failed to unzip content" + JVMUtil.lineSep() + JVMUtil.fromError(e));
		    }
		}catch (Exception e) {
			throw new RuntimeException("Failed to unzip content of '" + ArrayUtils.bytePrint(compressed) + "' " + JVMUtil.lineSep() + JVMUtil.fromError(e));
		}
  }

  public static byte[] unzip(final byte[] compressed, final boolean uselessVar) {
	  return unzip(compressed).getBytes(CharsetUtil.getCharset("UTF-8"));
  }

  public static byte[] unzip16(final byte[] compressed, final boolean uselessVar) {
	  return unzip16(compressed).getBytes(CharsetUtil.getCharset("UTF-16LE"));
  }

  public static boolean isZipped(final byte[] compressed) {
      return (compressed[0] == (byte) (GZIPInputStream.GZIP_MAGIC)) && (compressed[1] == (byte) (GZIPInputStream.GZIP_MAGIC >> 8));
  }

  public static String zipS(final String input) {
	return ByteUtils.toBase64(zip16(input));
  }
  
  public static String zipS16(final String input) {
	return ByteUtils.toBase64(zip16(input));
  }

  public static String unzipS(final String object) {	
	return unzip(ByteUtils.fromBase64(object));
  }
  
  public static String unzipS16(final String object) {	
	return unzip16(ByteUtils.fromBase64(object));
  }
  
  public static byte[] compress(byte[] data) {  
	  try {
		  Deflater deflater = new Deflater();  
		  deflater.setInput(data);  
		  deflater.setLevel(Deflater.BEST_COMPRESSION);
		  ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length);   
		  deflater.finish();  
		  byte[] buffer = new byte[2048];   
		  while (!deflater.finished()) {  
		   int count = deflater.deflate(buffer);
		   outputStream.write(buffer, 0, count);   
		  }  
		  outputStream.close();  
		  byte[] output = outputStream.toByteArray();  
		  return output;  
	  }catch (Exception e) {
		  e.printStackTrace();
		  return data;
	  }
  }  
  
  public static byte[] decompress(byte[] data) {  
	  try {
		  Inflater inflater = new Inflater();   
		  inflater.setInput(data);  
		  ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length);  
		  byte[] buffer = new byte[2048];  
		  while (!inflater.finished()) {  
		   int count = inflater.inflate(buffer);  
		   outputStream.write(buffer, 0, count);  
		  }  
		  outputStream.close();  
		  byte[] output = outputStream.toByteArray();   
		  return output;  
	  }catch (Exception e) {
		  e.printStackTrace();
		  return data;
	  }
  }  
}